import shutil
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
from snownlp import SnowNLP
import numpy as np
from flask import Flask, render_template, request, redirect, send_file,url_for,flash,jsonify,make_response
import matplotlib
matplotlib.use('Agg')  # 使用非交互式后端
from flask_sqlalchemy import SQLAlchemy
from matplotlib.font_manager import FontProperties
from matplotlib import pyplot as plt
import os
from collections import Counter
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
import pandas as pd
from werkzeug.utils import secure_filename
from wordcloud import WordCloud
from comments_crawler import crawl_data
from course_crawler import course_data
from flask import Flask, render_template, request, jsonify, session

# 设置字体属性
font_path = r"C:\Windows\Fonts\SimHei.ttf"  # 或更新为其他中文字体路径
font = FontProperties(fname=font_path)
app = Flask(__name__)
# 设置一个安全的随机密钥
app.secret_key = 'mooc'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'  # 未登录用户访问受保护页面时跳转到的视图

# 配置 SQLite 数据库路径
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(basedir,'data','database.db')}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 禁用事件系统，可以提升性能

# 初始化 SQLAlchemy
db = SQLAlchemy(app)

class User(db.Model, UserMixin):
    user_id = db.Column(db.String(80), unique=True, nullable=False,primary_key=True) #unique=True: 该参数表示该字段的值在数据库中必须是唯一的
    password = db.Column(db.String(128), nullable=False) #nullable=False: 该参数表示该字段不能为空
    is_admin = db.Column(db.Boolean, default=False)
    username=db.Column(db.String(100),nullable=True,default=None)
    email = db.Column(db.String(120), unique=True, nullable=True, default=None)  # 邮箱，唯一，允许为空
    address = db.Column(db.String(200), nullable=True, default=None)  # 地址
    phone_number = db.Column(db.String(20), nullable=True, default=None)  # 联系电话

    # 定义 get_id 方法返回用户唯一标识符
    def get_id(self):
        return self.user_id  # 这里返回用户的 username 作为唯一标识符

    @property
    def is_active(self):
        return True

# 在第一次请求之前创建所有表
@app.before_request
def create_tables():
    db.create_all()

# 定义从用户ID加载用户的回调函数
@login_manager.user_loader
def load_user(user_id):
    return User.query.filter_by(user_id=user_id).first()
@app.route("/")
def index():
    return render_template('index.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        user_id = request.form['user_id']
        password = request.form['password']
        confirm_password = request.form['confirm_password']

        # 验证密码
        if password != confirm_password:
            # flash('密码不匹配！', 'danger')
            return redirect(url_for('register'))

        # 检查用户账号是否已存在
        if User.query.filter_by(user_id=user_id).first():
            # flash('用户名已存在！', 'danger')
            return redirect(url_for('register'))

        # 创建用户并保存到数据库
        hashed_password = generate_password_hash(password, method='pbkdf2:sha256')
        new_user = User(user_id=user_id, password=hashed_password)
        db.session.add(new_user)
        db.session.commit()

        # flash('注册成功！请登录。', 'success')
        return redirect(url_for('login'))

    return render_template('index.html')  # 在 GET 请求时渲染注册页面

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        user_id = request.form['user_id']
        password = request.form['password']
        user = User.query.filter_by(user_id=user_id).first()

        # 验证用户和密码
        if user and check_password_hash(user.password, password):
            login_user(user)  # 登录用户
            if user.is_admin:
                return redirect(url_for('admin'))   # 如果是管理员，可以重定向到管理员页面
            else:
                return redirect(url_for('dashboard'))  # 修改为主页面
        else:
            # flash('用户名或密码错误！', 'danger')
            return redirect(url_for('login'))

    return render_template('index.html')  # 在 GET 请求时渲染登录页面

@app.route('/dashboard')
@login_required
def dashboard():
    user = db.session.get(User, current_user.user_id)
    # flash('亲爱的用户，欢迎您！', 'success')
    return render_template('dashboard.html',user=user)

@app.route('/logout', methods=['POST'])
@login_required  # 确保用户登录后才能访问
def logout():
    logout_user()  # 注销当前用户
    # flash('您已成功退出登录!', 'success')  # 提示信息
    return jsonify({'success': True, 'redirect_url': url_for('login')})  # 返回JSON响应

@app.route('/dashboard/center', methods=['GET', 'POST'])
@login_required
def center():
    user = db.session.get(User, current_user.user_id)  # 获取当前用户信息

    # 检查用户名是否与用户 ID 一致，如果为空才将用户名设置为用户 ID
    if not user.username or user.username != str(current_user.user_id):
        user.username = str(current_user.user_id)  # 将用户名设置为用户 ID
        db.session.commit()  # 提交更改

    if request.method == 'POST':
        # 更新用户信息
        user.username = request.form['username']
        user.email = request.form.get('email')  # 使用 .get() 避免 KeyError
        user.address = request.form.get('address')
        user.phone_number = request.form.get('phone_number')
        db.session.commit()  # 提交更改
        # flash('用户信息更新成功！', 'success')  # 可选：显示消息
        return redirect(url_for('center'))  # 重定向到个人中心

    return render_template('center.html', user=user)  # GET 请求返回用户信息

@app.route('/dashboard/account', methods=['GET', 'POST'])
@login_required
def account():
    user = db.session.get(User, current_user.user_id)  # 获取当前用户

    if request.method == 'POST':
        # 获取表单数据
        old_password = request.form['password']
        new_password = request.form['newpassword']
        confirm_password = request.form['confirmPassword']

        # 检查原密码是否正确
        if not check_password_hash(user.password, old_password):
            # flash('原密码错误，请重新输入', 'error')
            return redirect(url_for('account'))

        # 检查新密码和确认密码是否一致
        if new_password != confirm_password:
            # flash('新密码与确认密码不一致，请重新输入', 'error')
            return redirect(url_for('account'))

        # 更新密码并加密
        user.password = generate_password_hash(new_password, method='pbkdf2:sha256')
        db.session.commit()
        # flash('密码修改成功！', 'success')
        return redirect(url_for('login'))

    return render_template('account.html', user=user)


# 配置上传文件的保存路径
UPLOAD_FOLDER = 'uploads/'
STATIC_FOLDER = 'static/images/'
OUTPUT_FOLDER = 'static/images'
app.config['SECRET_KEY'] = 'your_secret_key'
ALLOWED_EXTENSIONS = {'csv', 'xlsx'}
kmeans = KMeans(n_clusters=3, n_init='auto')  # 使用未来版本默认的 'auto'

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['STATIC_FOLDER'] = STATIC_FOLDER
app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
# 清空文件夹
def clear_folder(folder_path):
    """清空文件夹内容"""
    if os.path.exists(folder_path):
        for filename in os.listdir(folder_path):
            file_path = os.path.join(folder_path, filename)
            if os.path.isfile(file_path):
                os.remove(file_path)
# 判断文件是否为允许的格式
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/clear-cache', methods=['POST'])
def clear_cache():
    try:
        # 定义需要清除的文件夹路径
        folders_to_clear = [
            app.config['UPLOAD_FOLDER'],
            os.path.join(app.config['OUTPUT_FOLDER'], 'wordcloud'),
            os.path.join(app.config['OUTPUT_FOLDER'], 'scorelist'),
            os.path.join(app.config['OUTPUT_FOLDER'], 'wordlist'),
        ]

        # 遍历并清空每个文件夹
        for folder in folders_to_clear:
            if os.path.exists(folder):
                for filename in os.listdir(folder):
                    file_path = os.path.join(folder, filename)
                    if os.path.isfile(file_path):
                        os.remove(file_path)  # 删除文件
                    elif os.path.isdir(file_path):
                        shutil.rmtree(file_path)  # 删除子文件夹

        return jsonify({'success': True})
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/exploration', methods=['GET', 'POST'])
#exploration 函数的作用是读取与处理上传的文件
def exploration():
    if request.method == 'POST':
        file = request.files['file']
        if file and allowed_file(file.filename):
            # 清空 uploads 文件夹
            if os.path.exists(app.config['UPLOAD_FOLDER']):
                # 删除 uploads 文件夹中的所有文件
                for filename in os.listdir(app.config['UPLOAD_FOLDER']):
                    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                    if os.path.isfile(file_path):
                        os.remove(file_path)  # 删除文件

            # 获取并保存新的文件
            filename = secure_filename(file.filename)
            file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            file.save(file_path)

            try:
                # 如果是 Excel 文件 (.xlsx)
                if filename.endswith('.xlsx'):
                    data = pd.read_excel(file_path, engine='openpyxl')
                elif filename.endswith('.csv'):
                    data = pd.read_csv(file_path, encoding='utf-8', errors='replace', error_bad_lines=False)

                data_dict = data.to_dict(orient='records')  # 转换成字典，便于在模板中渲染
                return render_template('exploration.html', data=data_dict)

            except Exception as e:
                # 如果读取文件时发生错误，使用 flash 来传递错误信息
                flash(f"读取文件时出错: {e}", 'error')

        else:
            flash("文件格式不支持！", 'error')

    return render_template('exploration.html', data=None)

def generate_wordlist_chart(text, wordlist_folder):
    # 停用词列表（可根据需求扩展）
    stop_words = {'的', '了', '是', '我', '也', '和', '有', '就', '不', '在', '他', '你', '她', '我们', '他们', '它', '这', '那'}
    # 分词处理
    words = text.split()
    # 过滤条件：去除停用词，限制词语长度为 2-5 个字
    filtered_words = [word for word in words if word not in stop_words and 2 <= len(word) <= 5]
    # 统计词频
    word_count = Counter(filtered_words)
    # 取前10个高频词
    most_common = word_count.most_common(10)
    if not most_common:
        raise ValueError("无有效词语可生成统计图")  # 如果没有有效词语，抛出异常
    labels, values = zip(*most_common)

    # 设置中文字体路径（修改为实际字体路径）
    font_path = r"C:\Windows\Fonts\SimHei.ttf"  # 替换为你系统中支持中文的字体路径
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用于正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号

    # 绘制柱状图
    plt.figure(figsize=(10, 6))
    plt.bar(labels, values, color='skyblue')
    plt.title('Top 10 Frequent Words', fontdict={'fontsize': 16})
    plt.ylabel('Frequency')
    plt.xlabel('Words')
    plt.xticks(rotation=45)
    wordlist_path = os.path.join(wordlist_folder, 'wordlist.png')
    plt.savefig(wordlist_path, bbox_inches='tight')
    plt.close()
    return wordlist_path

@app.route('/explore', methods=['POST'])
#explore 函数的作用是数据探索部分
def explore():
    # 上传的文件
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], os.listdir(app.config['UPLOAD_FOLDER'])[0])
    if not os.path.exists(file_path):
        return jsonify({'error': '上传文件夹中没有文件'}), 400

    try:
        # 读取文件
        if file_path.endswith('.xlsx'):
            data = pd.read_excel(file_path, engine='openpyxl')
        elif file_path.endswith('.csv'):
            data = pd.read_csv(file_path, encoding='utf-8')

        # 确保目标文件夹存在
        wordcloud_folder = os.path.join(app.config['OUTPUT_FOLDER'], 'wordcloud')
        wordlist_folder = os.path.join(app.config['OUTPUT_FOLDER'], 'wordlist')
        scorelist_folder = os.path.join(app.config['OUTPUT_FOLDER'], 'scorelist')
        for folder in [wordcloud_folder, wordlist_folder]:
            clear_folder(folder)

        # 生成词云图
        text_column = data['content']  # 假设内容列名为 'content'
        text = " ".join(str(entry) for entry in text_column)

        # 设置字体路径，确保支持中文（修改为你机器上的字体路径）
        font_path = r'C:\Windows\Fonts\SimHei.ttf'
        wordcloud = WordCloud(width=800, height=400, background_color='white', font_path=font_path).generate(text)
        wordcloud_path = os.path.join(wordcloud_folder, 'wordcloud.png')
        wordcloud.to_file(wordcloud_path)

        # 生成词频统计图
        wordlist_path = generate_wordlist_chart(text, wordlist_folder)
        # 生成评分统计图
        if 'mark' in data.columns:  # 确保存在 'mark' 列
            marks = data['mark'].dropna().astype(int)  # 清理数据并转为整数
            score_counts = marks.value_counts()  # 统计评分出现次数
            labels = score_counts.index.astype(str)  # 转为字符串作为标签
            sizes = score_counts.values  # 对应的数量

            # 绘制饼状图
            plt.figure(figsize=(8, 8))
            plt.pie(
                sizes,
                labels=labels,
                autopct='%1.1f%%',
                startangle=140,
                colors=plt.cm.Paired.colors  # 使用配色方案
            )
            plt.title('Course Score Distribution', fontdict={'fontsize': 16})
            scorelist_path = os.path.join(scorelist_folder, 'scorelist.png')
            plt.savefig(scorelist_path, bbox_inches='tight')
            plt.close()
        else:
            scorelist_path = None

        return jsonify({
            'wordcloud': os.path.join(wordcloud_folder, 'wordcloud.png'),
            'wordlist': os.path.join(wordlist_folder, 'wordlist.png'),
            'scorelist': os.path.join(scorelist_folder, 'scorelist.png')
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/sentiment-analysis', methods=['POST'])
def sentiment_analysis():
    try:
        # 上传的文件
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], os.listdir(app.config['UPLOAD_FOLDER'])[0])
        if not os.path.exists(file_path):
            return jsonify({'error': '上传文件夹中没有文件'}), 400

        # 读取评论数据
        if file_path.endswith('.xlsx'):
            data = pd.read_excel(file_path, engine='openpyxl')
        elif file_path.endswith('.csv'):
            data = pd.read_csv(file_path, encoding='utf-8')
        # 确保 'content' 列数据为字符串类型
        data['content'] = data['content'].astype(str)

        # 转换评论为小写
        data['content'] = data['content'].apply(lambda x: x.lower() if isinstance(x, str) else x)
        text_column = data['content']  # 假设评论内容列名为 'content'

        comments = text_column.dropna().tolist()  # 去除空值

        # 使用 TF-IDF 提取关键词
        vectorizer = TfidfVectorizer(max_features=100)
        tfidf_matrix = vectorizer.fit_transform(comments)
        keywords = vectorizer.get_feature_names_out()

        # 使用 SnowNLP 进行情感分析
        sentiments = [SnowNLP(comment).sentiments for comment in comments]

        # 使用 K-Means 聚类
        kmeans = KMeans(n_clusters=3, random_state=42)
        clusters = kmeans.fit_predict(tfidf_matrix)

        # 可视化：生成散点图
        plt.figure(figsize=(10, 6))
        for i in range(3):
            cluster_indices = np.where(clusters == i)
            plt.scatter(cluster_indices, np.array(sentiments)[cluster_indices], label=f'Cluster {i + 1}')
        plt.axhline(0.5, color='red', linestyle='--', label='Neutral Line')
        plt.title('Sentiment Analysis and Clustering', fontsize=16)
        plt.xlabel('Comment Index')
        plt.ylabel('Sentiment Score (0=Negative, 1=Positive)')
        plt.legend()
        plt.grid()

        # 保存图像
        sentiment_folder = os.path.join(app.config['OUTPUT_FOLDER'], 'sentiment')
        if not os.path.exists(sentiment_folder):
            os.makedirs(sentiment_folder)
        sentiment_chart_path = os.path.join(sentiment_folder, 'sentiment_chart.png')
        plt.savefig(sentiment_chart_path, bbox_inches='tight')
        plt.close()

        return jsonify({
            'success': True,
            'sentiment_chart': f'/static/images/sentiment/sentiment_chart.png',
        })
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500



@app.route('/crawler', methods=['GET', 'POST'])
def crawler():
    search_query = ''  # 搜索关键词
    comments = []  # 默认值为空列表，防止未定义变量错误

    if request.method == 'POST':
        search_query = request.form.get('query', '').strip()
        session['search_query'] = search_query
        if search_query:  # 如果有搜索关键词
            comments = crawl_data(search_query)  # 获取评论数据
        else:
            comments = []  # 如果没有搜索关键词，返回空评论列表

    # 渲染模板时传递 search_query 和 comments
    return render_template('crawler.html', search_query=search_query, comments=comments)


@app.route('/course_search', methods=['POST'])
def course_search():
    course_name = request.form.get('course_name')
    baseurl = f"https://www.icourse163.org/search.htm?search={course_name}"
    courses = course_data(baseurl)
    return render_template('crawler.html', course_query=course_name, courses=courses)

@app.route('/guide')
def guide():
    return render_template('guide.html')
    
if __name__ == '__main__' :
    app.run(debug=True)
    if not os.path.exists('uploads'):
        os.makedirs('uploads')
    if not os.path.exists('static/images'):
        os.makedirs('static/images')
